/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import { ArkMethod, ArkFile, ArkClass } from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects, IssueReport } from "../../model/Defects";
import { ClassMatcher, FileMatcher, MatcherCallback, MatcherTypes} from "../../matcher/Matchers";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
const defaultOptions: Options = {
    ignoreDifferentlyNamedParameters: false
}
interface Options {
    /** Whether two parameters with different names at the same index should be considered different even if their types are the same. */
    ignoreDifferentlyNamedParameters?: boolean;
}
const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'UnifiedSignaturesCheck');
const gmetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/unified-signatures-check.md", // TODO: support url
    description: "Disallow two overloads that could be unified into one with a union or an optional/rest parameter."
};
export class UnifiedSignaturesCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gmetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };
    private classMatcher: ClassMatcher = {
        file: [this.fileMatcher],
        matcherType: MatcherTypes.CLASS
    };
    public registerMatchers(): MatcherCallback[] {
        const matchClass: MatcherCallback = {
            matcher: this.classMatcher,
            callback: this.check
        }
        return [matchClass];
    }
    public check = (Class: ArkClass) => {
        // 将规则选项转换为Options类型
        let options: Options;
        if (this.rule && this.rule.option.length > 0) {
            options = this.rule.option[0] as Options;
        } else {
            options = defaultOptions;
        }
        let methodNameArray: string[] = [];
        let parameterTypeNameArray: string[] = [];
        let parameterNameArray: string[] = [];
        const methods = Class.getMethods();
        for (const method of methods) {
            let declareClass = method.getDeclaringArkClass();
            //获取当前方法的声明文件;
            const arkFile = declareClass.getDeclaringArkFile();
            const methodSignatures = method.getDeclareSignatures();
            if (methodSignatures) {
                if (methodSignatures.length == 1) {
                    continue;
                }
                for (const methodSignature of methodSignatures) {
                    const methodSubSignature = methodSignature.getMethodSubSignature();
                    const methodName = methodSubSignature.getMethodName();
                    methodNameArray.push(methodName);
                    const parameters = methodSubSignature.getParameters();
                    if (parameters.length > 0) {
                        for (const parameter of parameters) {
                            const parameterType = parameter.getType();
                            const parameteName = parameter.getName();
                            const parameterTypeName = parameterType.toString();
                            parameterTypeNameArray.push(parameterTypeName);
                            parameterNameArray.push(parameteName);
                        }
                    }
                }
                if (options.ignoreDifferentlyNamedParameters == false) {
                    for (let i = 0; i < methodNameArray.length - 1; i++) {
                        const firstElement = methodNameArray[0];
                        if (methodNameArray[i + 1] == firstElement) {
                            if (parameterTypeNameArray.length > 1) {
                                for (let j = i + 1; j < parameterTypeNameArray.length; j++) {
                                    const parameterTypeName = parameterTypeNameArray[j];
                                    this.addIssueReport(arkFile, method, parameterTypeName, parameterNameArray, j)
                                    parameterTypeNameArray = [];
                                }
                            }
                            else if (parameterTypeNameArray.length == 1) {
                                for (const methodSignature of methodSignatures) {
                                    const methodSubSignature = methodSignature.getMethodSubSignature();
                                    const parameters = methodSubSignature.getParameters();
                                    if (parameters.length > 0) {
                                        for (let parameter of parameters) {
                                            const dotdotdot = parameter.hasDotDotDotToken();
                                            if (dotdotdot == true) {
                                                for (let j = i + 1; j <= parameterTypeNameArray.length; j++) {
                                                    if (parameterTypeNameArray.length > 1) {
                                                        const parameterTypeName = parameterTypeNameArray[j];
                                                        this.addIssueReport(arkFile, method, parameterTypeName, parameterNameArray, j, dotdotdot)
                                                    }
                                                    else {
                                                        const parameterTypeName = parameterTypeNameArray[i];
                                                        this.addIssueReport(arkFile, method, parameterTypeName, parameterNameArray, j, dotdotdot)
                                                    }

                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                if (options.ignoreDifferentlyNamedParameters == true) {
                    for (let i = 0; i < methodNameArray.length - 1; i++) {
                        const firstElement = methodNameArray[0];
                        if (methodNameArray[i + 1] == firstElement) {
                            if (parameterTypeNameArray.length > 1) {
                                for (let j = i + 1; j < parameterTypeNameArray.length; j++) {
                                    const parameterTypeName = parameterTypeNameArray[j];
                                    for (let l = 0; l < parameterNameArray.length - 1; l++) {
                                        const firstParameterName = parameterNameArray[0];
                                        if (parameterNameArray.length <= 2) {
                                            if (parameterNameArray[l + 1] == firstParameterName) {
                                                this.addIssueReport(arkFile, method, parameterTypeName, parameterNameArray, j);
                                            }
                                        }
                                        else if (parameterNameArray.length > 2) {
                                            const firstParameterName = parameterNameArray[2];
                                            if (parameterNameArray[l + 3] == firstParameterName) {
                                                this.addIssueReport(arkFile, method, parameterTypeName, parameterNameArray, j);
                                            }
                                        }
                                    }
                                }
                                parameterTypeNameArray = [];
                            }
                            else if (parameterTypeNameArray.length == 1) {
                                for (const methodSignature of methodSignatures) {
                                    const methodSubSignature = methodSignature.getMethodSubSignature();
                                    const parameters = methodSubSignature.getParameters();
                                    if (parameters.length > 0) {
                                        for (let parameter of parameters) {
                                            const dotdotdot = parameter.hasDotDotDotToken();
                                            if (dotdotdot == true) {
                                                for (let j = i + 1; j <= parameterTypeNameArray.length; j++) {
                                                    if (parameterTypeNameArray.length > 1) {
                                                        const parameterTypeName = parameterTypeNameArray[j];
                                                        this.addIssueReport(arkFile, method, parameterTypeName, parameterNameArray, j, dotdotdot)
                                                    }
                                                    else {
                                                        const parameterTypeName = parameterTypeNameArray[i];
                                                        this.addIssueReport(arkFile, method, parameterTypeName, parameterNameArray, j, dotdotdot)
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    private addIssueReport(arkFile: ArkFile, method: ArkMethod, typeName: string, parameter: string[], colNum: number, dotdotdot?: boolean) {
        const severity = this.rule.alert ?? this.metaData.severity;
        const warnInfo = this.getLineAndColumn(arkFile, method, typeName, parameter, colNum, dotdotdot);
        if (warnInfo) {
            this.metaData.description = this.metaData.description.padEnd(130);
            const filePath = arkFile.getFilePath();
            let defects = new Defects(warnInfo.line, warnInfo.startCol, warnInfo.endCol, this.metaData.description, severity, this.rule.ruleId, filePath,
                this.metaData.ruleDocPath, true, false, false);
            this.issues.push(new IssueReport(defects, undefined));
            RuleListUtil.push(defects);
        } else {
            logger.debug('No valid line number found for issue report.');
        }
    }
    private getLineAndColumn(arkfile: ArkFile, method: ArkMethod, typeName: string, parameter: string[], colNum: number, dotdotdot?: boolean) {
        if (arkfile) {
            const code = arkfile.getCode() ?? '';
            const lines = code.split('\r\n');
            const methodLineCols = method.getDeclareLines()
            let lineNumber;
            if (methodLineCols && colNum < methodLineCols.length) {
                lineNumber = methodLineCols[colNum];
            }
            for (const line of lines) {
                const lineIndex = line.indexOf(typeName);
                let modifiedParameter;
                if (lineIndex !== -1) {
                    if (dotdotdot) {
                        modifiedParameter = "..." + parameter[0];
                    }
                    else {
                        modifiedParameter = "(" + parameter[1];
                    }
                    const LineIndex = line.indexOf(modifiedParameter);
                    if (LineIndex !== -1) {
                        let startCol;
                        if (dotdotdot) {
                            startCol = LineIndex + 1;
                        }
                        else {
                            startCol = LineIndex + 2;
                        }
                        const endCol = LineIndex + 1;
                        const originPath = arkfile.getFilePath();
                        if (lineNumber !== undefined) {
                            return { line: lineNumber, startCol, endCol, filePath: originPath }
                        }
                    }
                }
            }
        } else {
            logger.debug('originStmt or arkFile is null');
        }
        return null;
    }
}